package com.gao.b_stream.d;

import org.junit.Test;

import java.util.*;
import java.util.concurrent.ConcurrentMap;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author gao
 * @time 2020/07/23 16:36:32
 */

/*
    Stream 的核心在于Collectors，即对处理后的数据进行收集。Collectors 提供了非常多且强大的API，
    可以将最终的数据收集成List、Set、Map，甚至是更复杂的结构(这三者的嵌套组合)

    Collectors 提供了很多API，很多都是一些函数的重载，这里我个人将其分为三大类，如下：
    1. 数据收集：set、map、list
    2. 聚合归约：统计、求和、最值、平均、字符串拼接、规约
    3. 前后处理：分区、分组、自定义操作
 */
public class App {

    // 一、数据收集

    /*
        Collectors.toCollection() 将数据转成Collection，只要是Collection 的实现都可以，
        例如ArrayList、HashSet ，该方法接受一个Collection 的实现对象或者说Collection 工厂的入参。
     */
    @Test
    public void test() {
        List<Integer> list = Stream.generate(() -> (int) (Math.random() * 6) + 1)
                .limit(3)
                .collect(Collectors.toCollection(ArrayList::new));

        list.forEach(e -> System.out.println(e));

        System.out.println("-----");

        Set<Integer> set = Stream.generate(() -> (int) (Math.random() * 6) + 1)
                .limit(3)
                .collect(Collectors.toCollection(HashSet::new));

        set.forEach(e -> System.out.println(e));
    }

    /*
        Collectors.toList()和Collectors.toSet() 其实和Collectors.toCollection() 差不多，
        只是指定了容器的类型，默认使用ArrayList 和 HashSet。
     */
    @Test
    public void test2() {
        //List
        List<Integer> list = Stream.of(1, 2, 3, 4, 5, 6, 8, 9, 0)
                .collect(Collectors.toList());

        //Set
        Set<Integer> set = Stream.of(1, 2, 3, 4, 5, 6, 8, 9, 0)
                .collect(Collectors.toSet());

        System.out.println(list.getClass());
        System.out.println(set.getClass());
    }

    // toMap
    @Test
    public void test3() {
        Student s = new Student("20201001", "张珊珊");
        Student s2 = new Student("20201002", "李思思");
        Student s3 = new Student("20201003", "王微微");

        //Function.identity() 获取这个对象本身，那么结果就是Map<String,Student> 即 id->student
        //串行收集
        Map<String, Student> map = Stream.of(s, s2, s3)
                .collect(Collectors.toMap(e -> e.getId(), Function.identity()));

        map.forEach((k, v) -> System.out.println(k + ":" + v));

        System.out.println("-----");

        ConcurrentMap<String, Student> map2 = Stream.of(s, s2, s3)
                .collect(Collectors.toConcurrentMap(e -> e.getId(), Function.identity()));

        map2.forEach((k, v) -> System.out.println(k + ":" + v));


        System.out.println("-----");

        Map<String, String> map3 = Stream.of(s, s2, s3)
                .collect(Collectors.toMap(e -> e.getId(), e -> e.getName()));

        map3.forEach((k, v) -> System.out.println(k + ":" + v));

        System.out.println("-----");

        Map<String, String> map4 = Stream.of(s, s2, s3)
                .collect(Collectors.toConcurrentMap(e -> e.getId(), e -> e.getName()));

        map4.forEach((k, v) -> System.out.println(k + ":" + v));

    }

    // 如果key重复会怎么样？
    // 会抛出IllegalStateException异常！如下：
    @Test
    public void test4() {
        Student s = new Student("20201001", "张珊珊");
        Student s2 = new Student("20201002", "李思思");
        Student s3 = new Student("20201003", "王微微");
        Student s4 = new Student("20201003", "赵玲玲");

        Map<String, Student> map = Stream.of(s, s2, s3, s4)
                .collect(Collectors.toMap(e -> e.getId(), Function.identity()));

        map.forEach((k, v) -> System.out.println(k + ":" + v));
    }

    // 如果key重复，则取name较大的那个
    @Test
    public void test5() {
        Student s = new Student("20201001", "张珊珊");
        Student s2 = new Student("20201002", "李思思");
        Student s3 = new Student("20201003", "b");
        Student s4 = new Student("20201003", "a");

        Map<String, Student> map = Stream.of(s, s2, s3, s4)
                .collect(Collectors.toMap(e -> e.getId(),
                        Function.identity(),
                        (a, b) -> {
                            return a.getName().compareTo(b.getName()) > 0 ? a : b;
                        }
                ));

        map.forEach((k, v) -> System.out.println(k + ":" + v));
    }

    // 以上写法，也可以简写为以下形式
    @Test
    public void test6() {
        Student s = new Student("20201001", "张珊珊");
        Student s2 = new Student("20201002", "李思思");
        Student s3 = new Student("20201003", "z");
        Student s4 = new Student("20201003", "h");

        Map<String, Student> map = Stream.of(s, s2, s3, s4)
                .collect(Collectors.toMap(e -> e.getId(),
                        Function.identity(),
                        BinaryOperator.maxBy(Comparator.comparing(stu -> stu.getName()))
                ));

        map.forEach((k, v) -> System.out.println(k + ":" + v));
    }

    // 二、聚合规约

    // Collectors.joining()，通过append方法将元素拼接到一起，并且可以自定义分隔符、前缀、后缀
    @Test
    public void test7() {
        Student s = new Student("20201001", "张珊珊");
        Student s2 = new Student("20201002", "李思思");
        Student s3 = new Student("20201003", "王文文");

        String str = Stream.of(s, s2, s3)
                .map(Student::getId)
                .collect(Collectors.joining());

        System.out.println("str = " + str);

        str = Stream.of(s, s2, s3)
                .map(Student::getId)
                .collect(Collectors.joining("-"));

        System.out.println("str = " + str);

        str = Stream.of(s, s2, s3)
                .map(Student::getId)
                .collect(Collectors.joining("-", "[", "]"));

        System.out.println("str = " + str);
    }

    // Collectors.counting() 统计元素个数，这个和Stream.count() 作用都是一样的，返回的类型一个是包装Long，另一个是基本long
    @Test
    public void test8() {
        Long count = Stream.of(1, 2, 3, 4, 5, 6)
                .collect(Collectors.counting());

        long count2 = Stream.of(1, 2, 3, 4, 5, 6)
                .count();

        System.out.println("count = " + count);
        System.out.println("count2 = " + count2);
    }

    // Collectors.minBy()、Collectors.maxBy() 和Stream.min()、Stream.max() 作用也是一样的.
    @Test
    public void test9() {
        // maxBy 200
        Stream.of(1, 0, -10, 9, 8, 100, 200, -80)
                .collect(Collectors.maxBy(Integer::compareTo)).ifPresent(System.out::println);


        // max 200
        Stream.of(1, 0, -10, 9, 8, 100, 200, -80)
                .max(Integer::compareTo).ifPresent(System.out::println);

        // minBy -80
        Stream.of(1, 0, -10, 9, 8, 100, 200, -80)
                .collect(Collectors.minBy(Integer::compareTo)).ifPresent(System.out::println);

        // min -80
        Stream.of(1, 0, -10, 9, 8, 100, 200, -80)
                .min(Integer::compareTo).ifPresent(System.out::println);
    }


    /*
        Collectors.summingInt()、Collectors.summarizingLong()、Collectors.summarizingDouble() 这三个分别用于int、long、double类型数据
        一个求总操作，返回的是一个SummaryStatistics(求总)，包含了数量统计count、求和sum、最小值min、平均值average、最大值max。虽然IntStream、
        DoubleStream、LongStream 都可以是求和sum 但是也仅仅只是求和，没有summing结果丰富。如果要一次性统计、求平均值什么的，summing还是非常方便的。
     */
    @Test
    public void test10() {
        //IntSummaryStatistics{count=10, sum=55, min=1, average=5.500000, max=10}
        IntSummaryStatistics s = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
                .collect(Collectors.summarizingInt(Integer::valueOf));


        //DoubleSummaryStatistics{count=10, sum=55.000000, min=1.000000, average=5.500000, max=10.000000}
        DoubleSummaryStatistics s2 = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
                .collect(Collectors.summarizingDouble(Double::valueOf));


        //LongSummaryStatistics{count=10, sum=55, min=1, average=5.500000, max=10}
        LongSummaryStatistics s3 = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
                .collect(Collectors.summarizingLong(Long::valueOf));

        System.out.println("s = " + s);
        System.out.println("s2 = " + s2);
        System.out.println("s3 = " + s3);


        // 55
        int sum = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).mapToInt(Integer::valueOf)
                .sum();

        // 55.0
        double sum2 = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).mapToDouble(Double::valueOf)
                .sum();

        // 55
        long sum3 = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10).mapToLong(Long::valueOf)
                .sum();

        System.out.println("sum = " + sum);
        System.out.println("sum2 = " + sum2);
        System.out.println("sum3 = " + sum3);

    }

    // Collectors.reducing()方法的使用
    @Test
    public void test11() {
        Student s = new Student("20201001", "张珊珊");
        Student s2 = new Student("20201002", "李思思");
        Student s3 = new Student("20201003", "王文文");

        Integer r = Stream.of(s, s2, s3)
                .map(stu -> stu.getName().length())
                .collect(Collectors.reducing(0, (a, b) -> a + b));

        System.out.println("r = " + r);
    }

    // Collectors.groupingBy() 分组
    @Test
    public void test12() {
        Map<Object, List<Integer>> map = Stream.of(-6, -7, -8, -9, 1, 2, 3, 4, 5, 6)
                .collect(Collectors.groupingBy(i -> {
                    if (i < 0) return "小于";
                    else if (i == 0) return "等于";
                    else return "大于";
                }));

        System.out.println("map.getClass() = " + map.getClass());
        map.forEach((k,v) -> System.out.println(k + ":" + v));
    }

    // Collectors.groupingByConcurrent() 分组
    @Test
    public void test13() {
        Map<Object, List<Integer>> map = Stream.of(-6, -7, -8, -9, 1, 2, 3, 4, 5, 6)
                .collect(Collectors.groupingByConcurrent(i -> {
                    if (i < 0) return "小于";
                    else if (i == 0) return "等于";
                    else return "大于";
                }));

        System.out.println("map.getClass() = " + map.getClass());
        map.forEach((k,v) -> System.out.println(k + ":" + v));
    }


    // 自定义下一个维度收集器
    @Test
    public void test14() {
        Map<String, Set<Integer>> map = Stream.of(-6, -7, -8, -9, 1, 2, 3, 4, 5, 6)
                .collect(Collectors.groupingBy(i -> {
                    if (i < 0) return "小于";
                    else if (i == 0) return "等于";
                    else return "大于";
                }, Collectors.toSet()));

        System.out.println("map.getClass() = " + map.getClass());
        map.forEach((k,v) -> System.out.println(k + ":" + v));
    }

    // 自定义map容器类型，和下一个维度的收集器
    @Test
    public void test15() {
        LinkedHashMap<String, Set<Integer>> map = Stream.of(-6, -7, -8, -9, 1, 2, 3, 4, 5, 6)
                .collect(Collectors.groupingBy(i -> {
                    if (i < 0) return "小于";
                    else if (i == 0) return "等于";
                    else return "大于";
                }, LinkedHashMap::new, Collectors.toSet()));

        System.out.println("map.getClass() = " + map.getClass());
        map.forEach((k,v) -> System.out.println(k + ":" + v));
    }

    // Collectors.partitioningBy() 字面意思话就叫分区好了，但是partitioningBy最多只能将数据分为两部分，
    // 因为partitioningBy分区的依据Predicate，而Predicate只会有true 和false 两种结果，所有partitioningBy最多只能将数据分为两组。
    @Test
    public void test16() {
        Map<Boolean, List<Integer>> map = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
                .collect(Collectors.partitioningBy(i -> i > 5));

        System.out.println("map.getClass() = " + map.getClass());
        map.forEach((k,v) -> System.out.println(k + ":" + v));
    }

    // Collectors.mapping() 可以自定义要收集的字段。
    @Test
    public void test17() {
        Student s = new Student("20201001", "张珊珊");
        Student s2 = new Student("20201002", "李思思");
        Student s3 = new Student("20201003", "王文文");

        List<String> list = Stream.of(s, s2, s3)
                .collect(Collectors.mapping(stu -> stu.getName(), Collectors.toList()));

        list.forEach(e -> System.out.println(e));
    }

    // Collectors.collectingAndThen()收集后操作，如果你要在收集数据后再做一些操作，那么这个就非常有用了。
    // 这里在收集后转成了listIterator，只是个简单的示例，具体的实现逻辑非常有待想象。
    @Test
    public void test18() {
        Student s = new Student("20201001", "张珊珊");
        Student s2 = new Student("20201002", "李思思");
        Student s3 = new Student("20201003", "王文文");

        Iterator<String> it = Stream.of(s, s2, s3)
                .collect(
                        Collectors.collectingAndThen(
                                Collectors.mapping(stu -> stu.getName(), Collectors.toList()),
                                list -> list.iterator()
                        )
                );

        it.forEachRemaining(e -> System.out.println(e));
    }
}